package DAO.mysql;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;

import model.core.code.ClassData;
import model.core.code.CodeSeg;
import model.core.code.EntranceType;
import model.core.code.IEntrance;
import model.core.code.MethodData;
import model.core.code.MethodToken;
import model.core.code.TypeEntrance;
import model.core.code.Usage;
import model.core.code.VarEntrance;
import DAO.ICodeDAO;

public class MySQLCodeDAO implements ICodeDAO {
	private MySQLConnectionPool pool;
	
	public MySQLCodeDAO(MySQLConnectionPool connPool) {
		this.pool = connPool;
	}
	@Override
	public ClassData declareClass(String path) {
		ClassData clsData = new ClassData(path);
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			ClassData clsDt = this.getClassData(path);
			if(clsDt == null) {
				stmt = conn.prepareStatement("INSERT INTO srcData(srcIdentify) values(?)");
				stmt.setString(1, path);
				stmt.execute();
				rs = stmt.executeQuery("SELECT LAST_INSERT_ID()");
				if(rs.next()) {
					clsData.setClassID(rs.getInt(1));
				}
			} else {
				return clsDt;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(conn != null)
				pool.returnConnection(conn);
		}
		
		return clsData;
	}
	
	@Override
	public boolean saveDeclaredClass(ClassData data) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("UPDATE srcData set sourceCode=? where srcID=?");
			ByteArrayInputStream in = new ByteArrayInputStream(data.getSourceCode().getBytes());
			stmt.setBinaryStream(1, in);
			stmt.setInt(2, data.getClassID());
			stmt.execute();
			in.close();
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(conn != null)
				pool.returnConnection(conn);
		}
		return false;
	}

	@Override
	public boolean declareCodeSeg(CodeSeg codeSeg) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("INSERT INTO codeSeg(srcID,stLine,edLine,stColumn,edColumn) values(?,?,?,?,?)");
			stmt.setInt(1, codeSeg.getSrcID());
			stmt.setInt(2, codeSeg.getStartLine());
			stmt.setInt(3, codeSeg.getEndLine());
			stmt.setInt(4, codeSeg.getStartColumn());
			stmt.setInt(5, codeSeg.getEndColumn());
			stmt.execute();
			rs = stmt.executeQuery("SELECT LAST_INSERT_ID()");
			if(rs.next()) {
				codeSeg.setCodeID(rs.getInt(1));
			}
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(conn != null)
				pool.returnConnection(conn);
			
			
		}
		return false;
	}

	@Override
	public void addEntrance(MethodData method, IEntrance entrance, Usage usg) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("INSERT ignore INTO entrtomethod(entrID, methdID) values(?,?)");
			stmt.setInt(1, entrance.getID());
			stmt.setInt(2, method.getMethodID());
			stmt.execute();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}

	@Override
	public ClassData getClassData(int id) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("SELECT * from srcData where srcID = ?");
			stmt.setInt(1, id);
			rs = stmt.executeQuery();
			if(rs.next()) {
				int srcID = rs.getInt(1);
				String path = rs.getString(2);
				InputStream in = rs.getBinaryStream(3);
				byte[] temp = new byte[in.available()];
				in.read(temp);
				in.close();
				String src = new String(temp);
				ClassData clsDt = new ClassData(path);
				clsDt.setClassID(srcID);
				clsDt.setSourceCode(src);
				return clsDt;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	@Override
	public CodeSeg getCodeSegment(int id) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("SELECT * from codeSeg where codeID=?");
			stmt.setInt(1, id);
			rs = stmt.executeQuery();
			if(rs.next()) {
				int codeID = rs.getInt(1);
				int srcID = rs.getInt(2);
				int stLine = rs.getInt(3);
				int edLine = rs.getInt(4);
				int stClm = rs.getInt(5);
				int edClm = rs.getInt(6);
				CodeSeg seg = new CodeSeg(srcID, stLine, edLine, stClm, edClm);
				seg.setCodeID(codeID);
				return seg;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		
		return null;
	}

	@Override
	public MethodData getMethodData(int methodID) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("SELECT * FROM methods where methodID=?");
			stmt.setInt(1, methodID);
			rs = stmt.executeQuery();
			if(rs.next()) {
				int mthdID = rs.getInt(1);
				int codeID = rs.getInt(2);
				int methodToken = rs.getInt(3);
				CodeSeg cseg = this.getCodeSegment(codeID);
				MethodToken token = this.getMethodToken(methodToken);
				MethodData mthdData = new MethodData(cseg, token);
				mthdData.setMethodID(mthdID);
				return mthdData;
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	@Override
	public IEntrance declareEntrance(IEntrance ent) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("INSERT ignore INTO entrance(identify,type) values(?,?)");
			stmt.setString(1, ent.getName());
			stmt.setInt(2, ent.getEntranceType().ordinal());
			stmt.executeUpdate();
			IEntrance ret = this.getEntrance(ent.getName(), ent.getEntranceType());
			ent.setID(ret.getID());
			return ret;
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	@Override
	public List<IEntrance> getEntranceOfMethod(int methodID) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("SELECT * from entrtomethod where methdID=?");
			stmt.setInt(1, methodID);
			rs = stmt.executeQuery();
			List<IEntrance> ents = new LinkedList<IEntrance>();
			while(rs.next()) {
				ents.add(this.getEntrance(rs.getInt(1)));
			}
			return ents;
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	@Override
	public List<MethodData> getRelatedMethod(int entID) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("SELECT * from entrtomethod where entrID=?");
			stmt.setInt(1, entID);
			rs = stmt.executeQuery();
			List<MethodData> methods = new LinkedList<MethodData>();
			while(rs.next()) {
				methods.add(this.getMethodData(rs.getInt(1)));
			}
			return methods;
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	@Override
	public void connectEntrance(IEntrance var, IEntrance type) {
		Connection conn = null;
		PreparedStatement stmt = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("INSERT ignore INTO varType(typeID, varID) values(?,?)");
			stmt.setInt(1, type.getID());
			stmt.setInt(2, var.getID());
			stmt.execute();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}

	@Override
	public boolean declareMethodToken(MethodToken token) {
		Connection conn = null;
		PreparedStatement stmt = null;
		PreparedStatement stmt2 = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("INSERT ignore into methodToken(token) values(?)");
			stmt.setString(1, token.getSignature());
			int affected = stmt.executeUpdate();
			if(affected > 0) {
				rs = stmt.executeQuery("SELECT LAST_INSERT_ID()");
				if(rs.next()) {
					token.setTokenID(rs.getInt(1));
				}
			} else {
				stmt2 = conn.prepareStatement("SELECT * FROM methodToken where token=?");
				stmt2.setString(1, token.getSignature());
				rs = stmt2.executeQuery();
				if(rs.next()) {
					token.setTokenID(rs.getInt(1));
				}
			}
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return false;
	}

	@Override
	public boolean declareMethod(MethodData mthdData) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("INSERT ignore into methods(codeID, methodToken) values(?,?)");
			stmt.setInt(1, mthdData.getCodeID());
			stmt.setInt(2, mthdData.getMethodTokenID());
			stmt.execute();
			rs = stmt.executeQuery("SELECT LAST_INSERT_ID()");
			if(rs.next()) {
				mthdData.setMethodID(rs.getInt(1));
			}
			return true;
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return true;
	}

	@Override
	public void addCall(MethodData method, MethodToken called) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("INSERT ignore into mthdCall(methodID, mthdTokenID) values(?,?)");
			stmt.setInt(1, method.getMethodID());
			stmt.setInt(2, called.getTokenID());
			stmt.execute();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}
	@Override
	public ClassData getClassData(String path) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("SELECT * from srcData where srcIdentify=?");
			stmt.setString(1, path);
			rs = stmt.executeQuery();
			if(rs.next()) {
				int srcID = rs.getInt(1);
				InputStream in = rs.getBinaryStream(3);
				if(in == null) return null;
				byte[] temp = new byte[in.available()];
				in.read(temp);
				in.close();
				String src = new String(temp);
				ClassData clsDt = new ClassData(path);
				clsDt.setClassID(srcID);
				clsDt.setSourceCode(src);
				return clsDt;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}
	@Override
	public MethodToken getMethodToken(int tokenID) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("SELECT * from methodToken where tokenID=?");
			stmt.setInt(1, tokenID);
			rs = stmt.executeQuery();
			if(rs.next()) {
				String token = rs.getString(2);
				MethodToken tken = new MethodToken(token);
				tken.setTokenID(tokenID);
				return tken;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}
	
	private IEntrance createEntrance(EntranceType type, String token) {
		switch(type) {
		case VAR:
			return new VarEntrance(token, null);
		case TYPE:
			return new TypeEntrance(token);
		}
		return null;
	}
	
	@Override
	public IEntrance getEntrance(String token, EntranceType type) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("SELECT * from entrance where identify=? and type=?");
			stmt.setString(1, token);
			stmt.setInt(2, type.ordinal());
			rs = stmt.executeQuery();
			if(rs.next()) {
				int entrID = rs.getInt(1);
				IEntrance ent = createEntrance(type, token);
				ent.setID(entrID);
				return ent;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}
	@Override
	public IEntrance getEntrance(int entrID) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("SELECT * from entrance where entrID=?");
			rs = stmt.executeQuery();
			if(rs.next()) {
				String identify = rs.getString(2);
				int type = rs.getInt(3);
				IEntrance ent = createEntrance(EntranceType.values()[type], identify);
				ent.setID(entrID);
				return ent;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}
	@Override
	public List<IEntrance> getRelatedEntrance(IEntrance ent) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			stmt = conn.prepareStatement("SELECT * from varType where varID=?");
			stmt.setInt(1, ent.getID());
			rs = stmt.executeQuery();
			List<IEntrance> entrs = new LinkedList<IEntrance>();
			while(rs.next()) {
				entrs.add(this.getEntrance(rs.getInt(1)));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}
	@Override
	public MethodToken getMethodToken(String token) {
		Connection conn = null;
		PreparedStatement stmt = null;
		ResultSet rs = null;
		
		try {
			conn = pool.getConnection();
			if(!token.startsWith("[")) {
				token = "[" + token;
			}
			if(!token.endsWith("]")) {
				token = token + "]";
			}
			stmt = conn.prepareStatement("SELECT * from methodtoken where token=?");
			stmt.setString(1, token);
			rs = stmt.executeQuery();
			if(rs.next()) {
				int tkenID = rs.getInt(1);
				String tkenName = rs.getString(2);
				MethodToken mthdTken = new MethodToken(tkenName);
				mthdTken.setTokenID(tkenID);
				return mthdTken;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			if(conn != null)
				pool.returnConnection(conn);
			if(stmt != null) {
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if(rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}
}
